# -*- coding: utf-8 -*-
"""
Build an extension.

TODO: add target.
TODO: better error management
"""
from glob import fnmatch
from zipfile import ZipFile, ZIP_DEFLATED
from datetime import date
from os import linesep as EOL
from os.path import sep as SEP
import os, sys, fileinput, shutil, re, urllib, time


class BuildError(Exception): pass


IGNORE_DIRS = (
	"*.svn*",
	"*.git*",
	"*.cvs*",
	".%stest*" % SEP,
	".%sbuild*" % SEP,
	".%sdist*" % SEP,
	)

IGNORE_FILES = (
	"*.cache",
	"*.py",
	"*.pyc",
	"build.*",
	"Thumbs.db",
	".DS_STORE",
	"__MACOSX",
	)

FIX_EOL_FILES = (
	"*.html?",
	"*.php",
	"*.css",
	"*.js",
	"*.xml",
	"*.htaccess",
	)

TOKKEN_FILES = (
	"*.html?",
	"*.php",
	"*.css",
	"*.js",
	"*.xml",
	"*.txt",
	)


def get_details(default_path='./default.php'):
	"""
	Get the extension name and version from default.php
	and check them against lussumo.com add-on website.
	"""
	count = 0
	details = {}
	detail_search = re.compile("^\s*(Extension Name|Extension Url|Description|Version|Author|Author Url):(.+)$")
	try:
		for line in fileinput.input(files=(default_path,)):
			result = detail_search.match(line)
			if result:
				key, value = result.groups()
				value = value.strip()
				if key == 'Extension Name':
					details['EXT_NAME'] = value
				elif key == 'Extension Url':
					details['EXT_URL'] = value
				elif key == 'Description':
					details['DESCRIPTION'] = value
				elif key == 'Version':
					details['VERSION'] = value
				elif key == 'Author':
					details['AUTHOR'] = value
				elif key == 'Author Url':
					details['AUTHOR_URL'] = value


			count += 1
			if count >= 15:
				break
	except IOError, e:
		raise BuildError('Could not open "%s" and get the the extension details.\n%s' % (default_path, str(e)))

	try:
		details['DIST_NAME'] = '%s-%s' % (normalize_name(details['EXT_NAME']),details['VERSION'],)
		details['OLD_VERSION'], app_url = get_details_from_lussumo(details['EXT_NAME'])
	except KeyError:
		raise BuildError('Cannot find extensions details from "%s"' % default_path)
	except ValueError:
		sys.stderr.write('Cannot find extensions details from "%s"%s' % (default_path,EOL,))

	return details


def get_details_from_lussumo(extension_name):
	"""
	Gets the app details from lussumo.com
	"""
	params = urllib.urlencode({'Extension': extension_name})
	f = urllib.urlopen("http://lussumo.com/updatecheck/default.php?%s" % params)
	resp = re.compile("([-\.\d\w]+)\|(.+)").match(f.read())
	if resp:
		return resp.groups()
	else:
		return ()


def normalize_name(name):
	"""
	Used to capitalise the extension name and remove space.
	It make sure the extension name can be used for a folder name.

	>>> normalize_name('Low-Cal Vanilla')
	'LowCalVanilla'
	>>> normalize_name("Low-Cal Vanilla / That Guy's fork")
	'LowCalVanillaThatGuysFork'
	"""
	name = ''.join([w.capitalize() for w in re.sub("[-_\\/]+", ' ', name).split()])
	return re.sub("[^\w\d\.]+", '', name)


def normalize_path(path):
	"""
	Replace backslashes in path for slashes
	"""
	return path.replace("\\", '/').replace('/', SEP)


def clean(dirty_folders=('./build','./dist',)):
	"""
	remove build and dist folder
	"""
	try:
		for dir_name in dirty_folders:
			if os.path.exists(dir_name):
				print 'Removing "%s" and its contents...' % dir_name
				shutil.rmtree(dir_name)
	except (os.error, shutil.Error), e:
		raise BuildError("Cannot delete the dist and build folders.%s%s" % (EOL, str(e),))


def prepare(mk_folders=('./build','./dist',)):
	"""
	Create the dist and build folder.
	"""
	clean(mk_folders)
	try:
		for dir_name in mk_folders:
			if not os.path.exists(dir_name):
				print 'Creating "%s"...' % dir_name
				os.mkdir(dir_name)
	except (os.error, shutil.Error), e:
		raise BuildError("Cannot prepare the dist and build folders.%s%s" % (EOL, str(e),))


def copy_src(src_dir, dst_dir, ignore_dirs=IGNORE_DIRS, ignore_files=IGNORE_FILES):
	"""
	Copy files to BUILD_FOLDER_PATH and process them (eol and tokken conversion)
	"""
	src_dir = normalize_path(src_dir)
	dst_dir = normalize_path(dst_dir)
	ignore_dirs = [normalize_path(p) for p in ignore_dirs]

	if ignore_dirs:
		ignore_dirs = re.compile("(%s)" % '|'.join([fnmatch.translate(d) for d in ignore_dirs]))
	if ignore_files:
		ignore_files = re.compile("(%s)" % '|'.join([fnmatch.translate(f) for f in ignore_files]))

	for dir_name, sub_dirs, files in os.walk(src_dir):
		if not ignore_dirs or not ignore_dirs.match(normalize_path(dir_name)):
			# create subdirectory
			for sub_dir in [SEP.join((dir_name,d,)) for d in sub_dirs]:
				if not ignore_dirs.match(normalize_path(sub_dir)):
					copy_path = sub_dir.replace(src_dir, dst_dir, 1)
					print "Creating \"%s\"..." % copy_path
					os.mkdir(copy_path)
			# copy files
			for file_name in files:
				if not ignore_files or not ignore_files.match(file_name):
					file_path = SEP.join((dir_name,file_name,))
					copy_path = file_path.replace(src_dir, dst_dir, 1)
					print "Copying \"%s\" to \"%s\"..." % (file_path, copy_path)
					shutil.copyfile(file_path, copy_path)


def create_package(dst_file, src_dir, mode='w'):
	"""
	Create a zip package of the BUILD_FOLDER_PATH content

	TODO: find a way to create empty directory in the archive
	"""
	print "Creating \"%s\" archive from \"%s\"..." % (dst_file, src_dir,)
	z = ZipFile(dst_file, mode, ZIP_DEFLATED)
	for dir, sub_dirs, files in os.walk(src_dir):
		for file in files:
			file_path = '%s/%s' % (dir,file,)
			z.write(file_path, file_path.replace('%s%s' % (src_dir,SEP,),'',1))
	z.close()


def convert_eol(file_name='', src_dir='', eol='\n', fix_eol_files=FIX_EOL_FILES,):
	"""
	Fix the end of line of a file.
	"""
	if not file_name and not src_dir:
		print "convert_eol: No file to parse."

	if fix_eol_files:
		fix_eol_files = re.compile("(%s)" % '|'.join([fnmatch.translate(f) for f in fix_eol_files]))

	re_eol = re.compile("(\r\n|\r|\n)")

	if file_name:
		if os.path.isdir(file_name):
			sys.stderr.write('convert_eol: "%s" is not a file %s' % (file_name, EOL,))
		else:
			__convert_eol(file_name, re_eol, fix_eol_files, eol)

	if src_dir:
		if not os.path.isdir(src_dir):
			sys.stderr.write('translate_tokkens: "%s" is not a directory %s' % (dir_name, EOL,))
		else:
			for dir_name, sub_dirs, files in os.walk(src_dir):
				for file_name in files:
					__convert_eol(SEP.join((dir_name,file_name,)), re_eol, fix_eol_files, eol)


def __convert_eol(file_name, re_eol, fix_eol_files, eol):

	if not fix_eol_files.match(file_name):
		return

	data = open(file_name, "rb").read()
	if '\0' in data:
		sys.stderr.write('__convert_eol: "%s" content binary data %s' % (file_name, EOL,))
		return

	newdata = re_eol.sub(eol, data)
	if data != newdata:
		print 'eol fixed in %s' % file_name
		f = open(file_name, "wb")
		f.write(newdata)
		f.close()


def translate_tokkens(file_name='', src_dir='', tokkens={}, tokkens_files=TOKKEN_FILES):
	"""
	Replace keywords in files
	"""

	if not file_name and not src_dir:
		print "No file to parse for tokken."
		return

	if not tokkens or not tokkens_files:
		print "No tokkens to replace."
		return

	__tokkens = {}
	for tokken in tokkens.keys():
		__tokkens[tokken] = {
			're': re.compile('@@%s@@' % tokken),
			'replacement': tokkens[tokken]
			}

	tokkens_files = re.compile("(%s)" % '|'.join([fnmatch.translate(f) for f in tokkens_files]))


	if file_name:
		if os.path.isdir(file_name):
			sys.stderr.write('translate_tokkens: "%s" is not a file %s' % (file_name, EOL,))
		else:
			__translate_tokkens(file_name, __tokkens, tokkens_files)

	if src_dir:
		if not os.path.isdir(src_dir):
			sys.stderr.write('translate_tokkens: "%s" is not a directory %s' % (dir_name, EOL,))
		else:
			for dir_name, sub_dirs, files in os.walk(src_dir):
				for file_name in files:
					__translate_tokkens(SEP.join((dir_name,file_name,)), __tokkens, tokkens_files)


def __translate_tokkens(file_name, tokkens, tokkens_files):

	if not tokkens_files.match(file_name):
		return

	data = open(file_name, "rb").read()
	if '\0' in data:
		sys.stderr.write('__translate_tokken: "%s" content binary data %s' % (file_name,EOL,))
		return

	newdata = data
	for tokken in tokkens.keys():
		newdata = tokkens[tokken]['re'].sub(tokkens[tokken]['replacement'], newdata)

	if data != newdata:
		print 'Tokkens replaced in %s' % file_name
		f = open(file_name, "wb")
		f.write(newdata)
		f.close()




def build():
	# get extension info from default.php and lussumo.com
	details = get_details(default_path="./default.php")
	details['LICENCE']	= 'GPLv2'
	details['LICENCE_URL'] = 'http://www.gnu.org/licenses/old-licenses/gpl-2.0.html'
	details['YEAR']		= str(date.today().year)
	DIST_NAME			= details['DIST_NAME']
	BUILD_FOLDER_PATH	= SEP.join((BUILD_DIR, normalize_name(details['EXT_NAME']),))


	print "Building %s version %s." % (details['EXT_NAME'],details['VERSION'],)
	if details.has_key('OLD_VERSION'):
		if details['OLD_VERSION'] == details['VERSION']:
			sys.stderr.write('You are build the same version of the extension than the one on lussumo.com%s.' % EOL)
		else:
			print "The previous version was %s" % details['OLD_VERSION']

	# prepare build and dist folder
	prepare((BUILD_DIR, DIST_DIR, BUILD_FOLDER_PATH))

	# copy files
	copy_src(src_dir=SRC_DIR, dst_dir=BUILD_FOLDER_PATH)

	# replace tokkens
	translate_tokkens(src_dir=BUILD_FOLDER_PATH, tokkens=details)

	# fix eol
	convert_eol(src_dir=BUILD_FOLDER_PATH)

	# create package
	create_package('%s/%s.zip' % (DIST_DIR,DIST_NAME,), src_dir=BUILD_DIR)




if __name__ == '__main__':

	ROOT			= '.'
	SRC_DIR			= ROOT
	BUILD_DIR		= '%s%sbuild' % (ROOT,SEP)
	DIST_DIR		= '%s%sdist' % (ROOT,SEP)
	SRC_DEFAULT_PATH = '%s%sdefault.php' % (SRC_DIR,SEP)

	try:
		build()
	except BuildError, e:
		print e
		#exit(1)

	time.sleep(5)
